One of the main benefits of lifecycle methods in class components is that they allow for us to program many side effects. UseEffect simply lets us apply any side effect that we'd like to our functional components. A typical usage of side effects could be making network requests to another service, logging analytics, or updating DOM elements manually.
When using the useEffect hook, we're telling react that we want to do something immediately after rendering, so once we do render (or re-render) a component, react will call the function(s) within our useEffect and invoke it!
If we take our network calls from theAPI calls
module and convert them to using hooks, we get:
xxxxxxxxxx
531import React, { useState, useEffect } from "react";
2
3export const Reddit = props => {
4 const [allPosts, setAllPosts] = useState(startingArrays);
5 const [searchTerm, setSearchTerm] = useState("");
6 const [posts, setPosts] = useState(startingArrays);
7
8
9 // instead of using promises and `.then()s`, we can use
10 // asynchronous programming with async await.
11 const callWithFetch = async () => {
12 const response = await fetch(`r/${props.subreddit}.json`);
13 const json = await response.json();
14 const allPosts = json.data.children.map(obj => obj.data);
15
16 setAllPosts(allPosts);
17 setPosts(allPosts);
18 };
19
20 // Use effect allows for us to program side effects with our
21 // functional componennts. This way we can kick off a side
22 // effect like a network call. What should be noted is that
23 // this should be treated as if it were an equivalent to both
24 // component did mount, and component did update. Basically,
25 // it's a question of whether or not we called render.
26 useEffect(() => {
27 if (allPosts.length === 0) callWithFetch();
28 });
29
30 const handleSearch = event => {
31 setSearchTerm(event.target.value);
32 setPosts(allPosts.filter(post => post.title.includes(event.target.value)));
33 };
34
35 return (
36 <div>
37 <h1>r/{props.subreddit}</h1>
38 <div> FILTER THE CUTENESS </div>
39 <input type="text" value={searchTerm} onChange={handleSearch} />
40 <ul>
41 {posts.map(post => (
42 <li key={post.id}>
43 <br />
44 <img src={post.thumbnail} />
45 {post.title}
46 </li>
47 ))}
48 </ul>
49 ;
50 </div>
51 );
52};
53
Take note of the useEffect on line 26. Because useEffect gets kicked off after every render, we want to write some logic to make sure that we don't make the network calls, update our state, re-render, call useEffect, make another network call, update our state, re-render, call useEffect, make another network call, and so on infinitely. With a quick check of our state variable, we can quickly make sure we only call our network calls once.
While you could do everything in one single useEffect, to keep your code clean, you can use as many useEffects as you like! Suppose that you'd want to set the title of the page based upon the number of elements that were filtered by the handleSearch function. We could use the same useEffect, but making a network call has almost nothing to do with setting our page's title, so we should use a different useEffect:
xxxxxxxxxx
591import React, { useState, useEffect } from "react";
2
3export const Reddit = props => {
4 const [allPosts, setAllPosts] = useState(startingArrays);
5 const [searchTerm, setSearchTerm] = useState("");
6 const [posts, setPosts] = useState(startingArrays);
7
8
9 // instead of using promises and `.then()s`, we can use
10 // asynchronous programming with async await.
11 const callWithFetch = async () => {
12 const response = await fetch(`r/${props.subreddit}.json`);
13 const json = await response.json();
14 const allPosts = json.data.children.map(obj => obj.data);
15
16 setAllPosts(allPosts);
17 setPosts(allPosts);
18 };
19
20 // Use effect allows for us to program side effects with our
21 // functional componennts. This way we can kick off a side
22 // effect like a network call. What should be noted is that
23 // this should be treated as if it were an equivalent to both
24 // component did mount, and component did update. Basically,
25 // it's a question of whether or not we called render.
26 useEffect(() => {
27 if (allPosts.length === 0) callWithFetch();
28 });
29
30 // UPDATE THE TITLE!
31 // This code will get run after every update and update
32 // the title of the page/tab
33 useEffect(() => {
34 document.title = `Showing ${posts.length} posts`
35 })
36
37 const handleSearch = event => {
38 setSearchTerm(event.target.value);
39 setPosts(allPosts.filter(post => post.title.includes(event.target.value)));
40 };
41
42 return (
43 <div>
44 <h1>r/{props.subreddit}</h1>
45 <div> FILTER THE CUTENESS </div>
46 <input type="text" value={searchTerm} onChange={handleSearch} />
47 <ul>
48 {posts.map(post => (
49 <li key={post.id}>
50 <br />
51 <img src={post.thumbnail} />
52 {post.title}
53 </li>
54 ))}
55 </ul>
56 ;
57 </div>
58 );
59};
Separation of concerns is not necessary, but it does make finding and updating code significantly easier.